
import struct

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from ModbusRTU import CModbus
import sqlite3


class CGDKeypad:

    supportedDCMPProtocolVersion = 1
    supportedKeypadProtocolVersion = 1

    def __init__(self,  display, protocol, dbFileName = None):

        self.display = display
        self.protocol = protocol

        self.stringOnlyState = 0
        self.statusState = 1
        self.paramViewState = 2
        self.paramEditState = 3

        #self.state = self.statusState

        self.memMode = False
        self.active = False
        
        self.prevActiveMenu = -1
        self.prevActiveParam = -1
        self.activeMenu = 0
        self.activeParam = 0
        self.activeValue = 0
        self.activeDefault = 0
        self.activeFormat = 0
        self.activeMin = 0
        self.activeMax = 100

        self.driveState = 0
        self.tripActive = False
        self.stateString = ['Init','Under Voltage','Disable','Ready', 'Run','Decelerating', 'DC Braking', \
                            'AutoTune Wait', 'AutoTune Run', 'AutoTune Fnsh']
        self.unitString = ['', 'Hz', 'V', 'A', '%', 'W', 'ohm', 'KW', 'rpm', 'mh', 's', 'us', 'ms']

        self.caretPos = -1
        self.displayCaret = -1

#        self.prIndexMenu = 0
#        self.prIndexParam = 1
#        self.prIndexValue = 2
#        self.prIndexMin = 3
#        self.prIndexMax = 4
#        self.prIndexDefault = 5
#        self.prIndexUnit = 6
#        self.prIndexDF = 7

        self.flashOn = False
        self.count = 0

        self.slotA = bytearray(64)
        self.slotB = bytearray(64)
        
        self.slotA[0] = 0
        self.slotB[0] = 0
        
        self.buttonState = 0
        
        self.displayMode = 0
        self.driveStatus = 0
        self.activeSlot = 0
        self.activeValue = 0
        self.activeString = ''
        self.activeTrip = 0
        self.activeSubTrip = 0
        self.tripStrID = 0
        self.subTripStrID = 0
        self.tripStr = ''
        self.subTripStr = ''
                
        if dbFileName != None and dbFileName != '':
            cnx = sqlite3.connect(dbFileName)
            self.cursor = cnx.cursor()
        else:
            self.cursor = None


    def AddDatalessRequest(self, code, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB
            
        if dest[0] == 0:
            dest[0] = code
            return True
        else:
            return False
            
            
    def OffsetActiveParamValue(self, offset, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False
        
        dest[0] = 0x80
        dest[1] = 4
        dest[2:6] = struct.pack('>i', offset)
        
        return True
        
    def GetStringForAlarmCode(self, alarmCode, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False       
           
        dest[0] = 0x82
        dest[1] = 1
        dest[2] = alarmCode & 0xFF
        return True
        
        
    def GetStringForStatusCode(self, statusCode, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False       
        
        dest[0] = 0x83
        dest[1] = 1
        dest[2] = statusCode & 0xFF
        return True
        
    def NavigateToRelativeMenu(self, offset, slot):
    
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False     

        dest[0] = 0x85
        dest[1] = 1
        dest[2] = offset & 0xFF
        return True
        
        
    def NavigateToRelativeParameter(self, offset, mode, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False    

        dest[0] = 0x86
        dest[1] = 2
        dest[2] = offset & 0xFF
        dest[3] = mode & 0xFF
        return True
        
        
    def ChangeActiveParameter(self, menu, param, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False            

        dest[0] = 0x87
        dest[1] = 2
        dest[2] = menu & 0xFF
        dest[3] = param & 0xFF
        return True
        
        
    def ChangeModeToParamView(self, action, slot):
        
        if slot == 'A':
            dest = self.slotA
        else:
            dest = self.slotB 
            
        if dest[0] != 0:
            return False       
            
        dest[0] = 0x8a
        dest[1] = 1
        dest[2] = action & 0xFF
        return True
        
        
    def CommWithDrive(self):
        
        data = bytearray()
        
        data.append(self.buttonState >> 8)
        data.append(self.buttonState & 0xFF)
        
        if self.slotA[0] == 0:
            self.AddDatalessRequest(0x03, 'A')
            
        if (self.slotA[0] & 0x80) == 0:
            data.append(self.slotA[0])
        else:
            data = data + self.slotA[0:self.slotA[1] + 2]
        self.slotA[0] = 0
            
        if self.slotB[0] != 0:
            data = data + self.slotB[0:self.slotB[1] + 2]
            self.slotB[0] = 0
        else:
            data.append(0)
                    
        
        [err, data] = self.protocol.ProtocolWrapper(CModbus.FC_KEYPAD, data)
        
        if err == 0:
            
            self.tripActive = ((data[0] & 0x08) != 0)
            self.displayMode = data[0] & 0x07
            self.driveState = data[1] & 0x7F
            
            if len(data) > 2:
                self.ProcessResponse(data[2:4+data[3]])
                
                if len(data) > 4 + data[3] :                    
                    self.ProcessResponse(data[4 + data[3]:])
                
                
    def ProcessResponse(self, data):
        
        if data[0] == 0x03 or data[0] == 0x08 or data[0] == 0x85 or data[0] == 0x86 or data[0] == 0x87:
            
            self.activeFormat = struct.unpack('>I', data[2:6])[0]
            self.activeMenu = data[7]
            self.activeParam = data[8]
            
            if (self.activeFormat & 0x00100000) != 0:
                fmt = '>i'
            else:
                fmt = '>I'
                
            self.activeMax = struct.unpack(fmt, data[9:13])[0]
            self.activeMin = struct.unpack(fmt, data[13:17])[0]
            self.activeDefault = struct.unpack(fmt, data[17:21])[0]
            self.activeValue = struct.unpack(fmt, data[21:25])[0]
            
            
        elif data[0] == 0x04:
            
            self.activeAlarms = data[2:2+data[3]]

        elif data[0] == 0x05:
            
            self.activeTrip = (data[2] << 8) + data[3]
            self.tripStrID = (data[4] << 8) + data[5]
            tripStrLen = data[6]
            if tripStrLen != 0:
                self.tripStr = struct.unpack('s>', data[7:7+tripStrLen])[0]
            else:
                self.tripStr = ''
            
            subTripOffset = 7 + tripStrLen
            self.activeSubTrip = (data[subTripOffset] << 8) + data[subTripOffset+1]
            self.subTripStrID = (data[subTripOffset+2] << 8) + data[subTripOffset+3]
            tripStrLen = data[subTripOffset+4]
            if tripStrLen != 0:
                self.subTripStr = struct.unpack('s>', data[7:7+tripStrLen])[0]
            else:
                self.subTripStr = ''                
            
            
        elif data[0] == 0x07:
            
            self.activeString = data[2:].decode('ascii')


    def Start(self):
        self.state = self.statusState
        #self.GetCurrentStatusParam()


    def UpdateDisplay(self):
        
        self.CommWithDrive()

        self.count = self.count + 1
        if self.count > 2:
            self.flashOn  = not self.flashOn
            self.count = 0

        stateStr = ''
        funcNumberStr = ''
        valueStr = ''
        

        if self.displayMode == self.stringOnlyState:
            self.AddDatalessRequest(0x07, 'B')
            stateStr = self.activeString
            
        elif self.displayMode == self.statusState:
            
            if self.tripActive == True:
                self.AddDatalessRequest(0x05, 'A')
                valueStr = 'Err:%02x.%d'%(self.activeTrip, self.activeSubTrip)
            else:
                valueStr = self.GetActiveValueString()


        elif self.displayMode == self.paramViewState or self.displayMode == self.paramEditState:

            funcNumberStr = 'F%02d.%03d'%(self.activeMenu, self.activeParam)

            valueStr = self.GetActiveValueString()
            
            
        if self.tripActive:
            stateStr = 'Trip'
        else:
            stateStr = self.stateString[self.driveState]
        
        if self.tripActive and not self.flashOn:
            stateStr = ''            
            
        if funcNumberStr != '':
            self.display.WriteInfo(stateStr + '\n' + funcNumberStr)
        else:
            self.display.WriteInfo(stateStr)
            
        self.display.WriteValue(valueStr)

            
        #--------------------Upate param description-----------------------------
        if self.prevActiveMenu != self.activeMenu or self.prevActiveParam != self.activeParam:
            
            self.prevActiveMenu = self.activeMenu
            self.prevActiveParam = self.activeParam   
            
            dp = (self.activeFormat >> 13) & 0x07
            if dp == 0:
                description = 'Range: (%d, %d) '%(self.activeMin, self.activeMax)
                description = description + 'Default: %d\n'%self.activeDefault
            else:
                description = 'Range: (%f, %f) '%(self.activeMin/(10**dp), self.activeMax/(10**dp))
                description = description + 'Default: %f\n'%(self.activeDefault/(10**dp))
        
            if self.cursor != None:
            
                sql = 'SELECT ParamName, Description FROM ParameterTable WHERE Menu=%d AND Param=%d'%(self.activeMenu, self.activeParam)
                self.cursor.execute(sql)
               
                row = self.cursor.fetchone()
                if row != None:
                    description = description + row[0] + '\n'
                    if row[1] != None:
                        description = description + row[1]
            
            self.display.UpdateDescription(description)



    def GetActiveValueString(self):

        s = ''

        unit = (self.activeFormat >> 7) & 0x3F
        dp = (self.activeFormat >> 13) & 0x07
        df = (self.activeFormat >> 16) & 0x0F

        if df == 0:
            s = '% *.*f%-4s'%(10-dp, dp, self.activeValue/(10**dp), self.unitString[unit])
        # Version number
        elif df == 7:
            s = '%02d.%02d.%02d.%02d'%(self.activeValue/1000000, \
                                      (self.activeValue%1000000)/10000, \
                                      (self.activeValue%10000)/100, \
                                       self.activeValue%100)
        else:
            s = '0x% 8x'%(self.activeValue)
            
        if self.displayMode == self.paramEditState and not self.flashOn:
            
            if self.caretPos < 0:
                self.caretPos = 0
                
            if df == 10 or df == 11:
                l = list(s)
                if l[len(l) - self.caretPos - 1] == ' ':
                    l[len(l) - self.caretPos - 1] = '_'
                else:
                    l[len(l) - self.caretPos - 1] = ' '
                s = ''.join(l)
                
            else:
                l = list(s)
                
                caret = self.caretPos
                if self.caretPos >= dp and dp != 0:
                    caret = caret + 1
                
                if l[len(l) - caret - 1 - 4] == ' ':
                    l[len(l) - caret - 1 - 4] = '_'
                else:
                    l[len(l) - caret - 1 - 4] = ' '
                s = ''.join(l)


#        if self.displayMode == self.paramEditState and not self.flashOn:
#            
#            if self.caretPos < 0:
#                self.caretPos = 0
#                
#            if df == 10 or df == 11:
#                s = '0x% 8x'%self.activeValue
#                l = list(s)
#                if l[len(l) - self.caretPos - 1] == ' ':
#                    l[len(l) - self.caretPos - 1] = '_'
#                else:
#                    l[len(l) - self.caretPos - 1] = ' '
#                s = '%s'%(''.join(l))
#                
#            else:
#                s = '% 10d'%self.activeValue
#                l = list(s)
#                if l[len(l) - self.caretPos - 1] == ' ':
#                    l[len(l) - self.caretPos - 1] = '_'
#                else:
#                    l[len(l) - self.caretPos - 1] = ' '
#                if dp != 0:
#                    l.insert(10-dp, '.')
#                s = '%s%-4s'%(''.join(l), self.unitString[unit])
#
#        else:
#            if df == 0:
#                s = '% *.*f%-4s'%(10-dp, dp, self.activeValue/(10**dp), self.unitString[unit])
#            else:
#                s = '0x% 8x'%(self.activeValue)

        return s

    def EnterAction(self):

        if self.displayMode == self.statusState:
            self.ChangeModeToParamView(1, 'A')
        elif self.displayMode == self.paramViewState:
            self.AddDatalessRequest(0x0F, 'A')
            self.caretPos = 0
            self.displayCaret = 0
            
        elif self.displayMode == self.paramEditState:
            self.ChangeModeToParamView(1, 'A')
            self.caretPos = -1
            self.displayCaret = -1
            
    def EscAction(self):

        if self.displayMode == self.paramEditState :
            self.ChangeModeToParamView(0, 'A')
            self.caretPos = -1
            self.displayCaret = -1
        elif self.displayMode == self.paramViewState:
            self.AddDatalessRequest(0x01, 'A')


    def LeftAction(self):

        if self.displayMode == self.paramViewState:

            self.NavigateToRelativeMenu(-1, 'A')

        elif self.displayMode == self.paramEditState:

            self.caretPos = self.caretPos + 1
            if self.caretPos == 10:
                self.caretPos = 0

        #TODO: GetPrevStatusParam
        #elif self.state == self.statusState:
            #self.RequestToState(self.paramViewState)

    def RightAction(self):

        if self.displayMode == self.paramViewState:

            self.NavigateToRelativeMenu(1, 'A')

        elif self.displayMode == self.paramEditState:

            self.caretPos = self.caretPos - 1
            if self.caretPos == -1:
                self.caretPos = 10


    def UpAction(self):

        if self.displayMode == self.paramViewState:

            self.NavigateToRelativeParameter(1, 0, 'A')

        elif self.displayMode == self.paramEditState:
            
            df = (self.activeFormat >> 16) & 0x0F
            
            if df == 10 or df == 11:
                offset = 1 << (self.caretPos * 4)
            else:
                offset = 10**self.caretPos

            self.OffsetActiveParamValue(offset, 'A')
            


    def DownAction(self):

        if self.displayMode == self.paramViewState:

            self.NavigateToRelativeParameter(-1, 0, 'A')

        elif self.displayMode == self.paramEditState:

            df = (self.activeFormat >> 16) & 0x0F
            
            if df == 10 or df == 11:
                offset = -(1 << (self.caretPos * 4))
            else:
                offset = -10**self.caretPos

            self.OffsetActiveParamValue(offset, 'A')


    def RunPressed(self):
        self.buttonState |= 1
        
    def RunReleased(self):
        self.buttonState &= 0xFFFE

    def RunAction(self, dir):
        
        #todo
        #cw = 0x03
        #self.protocol.WriteSingle(0x3200, cw)
        pass

    def StopPressed(self):
        self.buttonState |= 0x02
        
    def StopReleased(self):
        self.buttonState &= 0xFFFD

    def StopAction(self, stopMode):
        #cw = 0x02c6 - stopMode
        #self.protocol.WriteSingle(0x3200, cw)
        pass


    def DumpParam(self, hFile, NonDefaultOnly):
        self.slotA[0] = 0
        self.ChangeModeToParamView(0, 'A')
        self.CommWithDrive()
        
        if self.displayMode != self.paramViewState:
            QMessageBox.critical("Failed to go to parameter view mode!")
            return
        
        # Set start parameter number to #1.0
        self.ChangeActiveParameter(1, 0, 'A')
        self.CommWithDrive()
        
        if NonDefaultOnly and self.activeValue == self.activeDefault:
            pass
        else:
            s = "%d,\t%d,\t0x%08x,\t%d\n"%(self.activeMenu, self.activeParam, self.activeFormat, self.activeValue)
            hFile.write(s)
            print(s)
            
        
        firstMenu = self.activeMenu
        while 1:
            self.DumpParamInMenu(hFile, NonDefaultOnly)
            self.CommWithDrive()
            self.NavigateToRelativeMenu(1, 'A')
            self.CommWithDrive()
            
            if self.activeMenu == firstMenu:
                break
            

    def DumpParamInMenu(self, hFile, NonDefaultOnly):
        
        firstParam = self.activeParam

        while 1:

            self.NavigateToRelativeParameter(1, 1, 'A')
            self.CommWithDrive()
            
            if self.activeParam == firstParam:
                break
                
            if NonDefaultOnly and self.activeValue == self.activeDefault:
                pass
            else:
                s = "%d,\t%d,\t0x%08x,\t%d\n"%(self.activeMenu, self.activeParam, self.activeFormat, self.activeValue)
                hFile.write(s)
                print(s)
            
